package com.smart.mac.utls;

import cn.hutool.core.collection.CollectionUtil;
import org.flowable.bpmn.model.*;
import org.flowable.task.api.Task;

import java.util.*;

/**
 * FlowUtils
 *
 * @Author mac
 */
public class FlowUtils {

    /**
    * 获取所有节点，包含子节点
    * @param: [flowElements, allElements]
    * @return: java.util.Collection<org.flowable.bpmn.model.FlowElement>
    * @author mac
    */
    public static Collection<FlowElement> getAllFlowElements(Collection<FlowElement> flowElements, Collection<FlowElement> allElements){
        allElements = CollectionUtil.isEmpty(allElements) ? new ArrayList<>() : allElements;
        for (FlowElement element : flowElements){
            allElements.add(element);
            //子流程，继续遍历
            if (element instanceof SubProcess){
                allElements = getAllFlowElements(((SubProcess)element).getFlowElements(),allElements);
            }
        }
        return allElements;
    }

    /**
    * 遍历校验目标节点
    * @param: [source  源节点, targetKey 目标节点, hasSequenceFlow 已经经过的连线的ID, isSequential 是否串行]
    * @return: java.lang.Boolean
    * @author mac
    */
    public static Boolean eachCheckRefTarget(FlowElement source, String targetKey, Set<String> hasSequenceFlow, Boolean isSequential) {
        isSequential = isSequential == null || isSequential;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        //如果是开始节点，且存在上级子系节点，则继续遍历
        if (source instanceof StartEvent && source.getSubProcess() != null){
            isSequential = eachCheckRefTarget(source.getSubProcess(),targetKey,hasSequenceFlow,isSequential);
        }
        //获取连线入口
        List<SequenceFlow> incomeSequenceFlows = getIncomeSequenceFlows(source);
        if (CollectionUtil.isNotEmpty(incomeSequenceFlows)) {
            for (SequenceFlow sequenceFlow : incomeSequenceFlows){
                //如果已经存在，则忽略
                if (hasSequenceFlow.contains(sequenceFlow.getId())){
                    continue;
                }
                //如果和目标节点是并行的，则直接返回，
                if (!isSequential){
                    break;
                }
                hasSequenceFlow.add(sequenceFlow.getId());
                //如果已经走过目标节点，则忽略
                if (sequenceFlow.getSourceFlowElement().getId().equals(targetKey)){
                    continue;
                }
                //如果是开始节点，直接结束
                if (sequenceFlow.getSourceFlowElement() instanceof StartEvent){
                    isSequential = false;
                    break;
                }
                //否则继续迭代
                isSequential = eachCheckRefTarget(sequenceFlow.getSourceFlowElement(),targetKey,hasSequenceFlow,isSequential);
            }
        }
        return isSequential;
    }

    /**
    * 获取连线入口
    * @param: [source]
    * @return: java.util.List<org.flowable.bpmn.model.SequenceFlow>
    * @author mac
    */
    public static List<SequenceFlow> getIncomeSequenceFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = null;
        if (source instanceof Gateway) {
            sequenceFlows = ((Gateway)source).getIncomingFlows();
        } else if (source instanceof SubProcess) {
            sequenceFlows = ((SubProcess)source).getIncomingFlows();
        } else if (source instanceof StartEvent) {
            sequenceFlows = ((StartEvent)source).getIncomingFlows();
        } else if (source instanceof EndEvent) {
            sequenceFlows = ((EndEvent)source).getIncomingFlows();
        } else if (source instanceof FlowNode){
            sequenceFlows = ((FlowNode)source).getIncomingFlows();
        }
        return sequenceFlows;
    }

    /**
    * 遍历查找目标之后的任务节点
    * @param: [target, runTaskKeyList, hasSequenceFlow, userTaskList]
    * @return: java.util.List<org.flowable.bpmn.model.UserTask>
    * @author mac
    */
    public static List<UserTask> eachFindChildUserTasks(FlowElement target, List<String> runTaskKeyList, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;

        //如果是开始节点，且存在上级子系节点，则继续遍历
        if (target instanceof EndEvent && target.getSubProcess() != null) {
            userTaskList = eachFindChildUserTasks(target.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
        }
        List<SequenceFlow> elementOutgoingFlows = getElementOutgoingFlows(target);
        if (CollectionUtil.isNotEmpty(elementOutgoingFlows)){
            for (SequenceFlow sequenceFlow : elementOutgoingFlows){
                // 如果发现连线重复,则跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // 添加已经走过的连线
                hasSequenceFlow.add(sequenceFlow.getId());
                //如果是连线是任务节点，则添加
                if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains(sequenceFlow.getTargetFlowElement().getId())){
                    userTaskList.add((UserTask)sequenceFlow.getTargetFlowElement());
                    continue;
                }
                //如果是子流程，则从第一个节点开始
                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess){
                    Collection<FlowElement> elements = ((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements();
                    FlowElement[] temp = new FlowElement[elements.size()];
                    temp = elements.toArray(temp);
                    List<UserTask> childUserTaskList = eachFindChildUserTasks(temp[0],runTaskKeyList,
                            hasSequenceFlow,null);
                    if (CollectionUtil.isNotEmpty(childUserTaskList)) {
                        userTaskList.addAll(childUserTaskList);
                        continue;
                    }
                }
                // 继续迭代
                userTaskList = eachFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
            }
        }
        return userTaskList;
    }

    /**
    * 根据节点，获取出口连线
    * @param: [source]
    * @return: java.util.List<org.flowable.bpmn.model.SequenceFlow>
    * @author mac
    */
    public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = null;
        if (source instanceof Gateway) {
            sequenceFlows = ((Gateway) source).getOutgoingFlows();
        } else if (source instanceof SubProcess) {
            sequenceFlows = ((SubProcess) source).getOutgoingFlows();
        } else if (source instanceof StartEvent) {
            sequenceFlows = ((StartEvent) source).getOutgoingFlows();
        } else if (source instanceof EndEvent) {
            sequenceFlows = ((EndEvent) source).getOutgoingFlows();
        } else if (source instanceof FlowNode) {
            sequenceFlows = ((FlowNode) source).getOutgoingFlows();
        }
        return sequenceFlows;
    }

    /**
    * 查找任务节点
    * @param: [source, passRoads, hasSequenceFlow, roads]
    * @return: java.util.List<org.flowable.bpmn.model.UserTask>
    * @author mac
    */
    public static List<UserTask> findRoad(FlowElement source,Set<String> hasSequenceFlow,List<UserTask> roads) {
        roads = roads == null ? new ArrayList<>() : roads;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;

        // 如果该节点为开始节点存在上级子节点，则继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            roads = findRoad(source.getSubProcess(), hasSequenceFlow, roads);
        }
        // 根据类型，获取入口连线
        List<SequenceFlow> sequenceFlows = getIncomeSequenceFlows(source);
        if (CollectionUtil.isNotEmpty(sequenceFlows)){
            for (SequenceFlow sequenceFlow : sequenceFlows){
                //已存在，则跳过
                if (hasSequenceFlow.contains(sequenceFlow.getId())){
                    continue;
                }
                hasSequenceFlow.add(sequenceFlow.getId());
                if (sequenceFlow.getSourceFlowElement() instanceof UserTask){
                    roads.add((UserTask)sequenceFlow.getSourceFlowElement());
                    continue;
                }
                roads = findRoad(sequenceFlow.getSourceFlowElement(),hasSequenceFlow,roads);
            }
        }
        return roads;
    }
}
